﻿using System;
using System.Collections.Generic;
using System.Reflection;
using System.Linq;

namespace OMap.Tools {

    /// <summary>
    /// This class contains some miscellaneous helper methods for .NET Type operations
    /// </summary>
    public static class TypeHelper {

        /// <summary>
        /// Returns <value>true</value> if it is supported simple type
        /// </summary>
        /// <param name="type">The type to check</param>
        /// <returns>
        /// 	<c>true</c> if [is supported simple type] [the specified type]; otherwise, <c>false</c>.
        /// </returns>
        public static bool IsSimpleType(Type type) {

            if (type.IsPrimitive ||
                type == typeof(string) ||
                type == typeof(DateTime) ||
                type == typeof(Guid) ||
                (type.IsGenericType &&
                    (type.GetGenericTypeDefinition() == typeof(IList<>) ||
                    type.GetGenericTypeDefinition() == typeof(List<>))) ||
                type == typeof(Array) ||
                type.IsEnum ||
                type == typeof(decimal) ||
                type == typeof(object)) {
                return true;
            }

            return false;

        }

        public static object GetDefaultValue(Type type) {
            if(type.IsValueType) {
                return Activator.CreateInstance(type);
            }
            return null;
        }

		/// <summary>
		/// Determines whether the passed Typ is a nullable type.
		/// </summary>
		/// <param name="theType">The type to check.</param>
		/// <returns></returns>
		public static bool IsNullableType(Type theType) {
			return (theType.IsGenericType && theType.
			  GetGenericTypeDefinition().Equals
			  (typeof(Nullable<>)));
		}

        public static bool HasDefaultValue(object value,Type type) {
            if(type == null) {
                throw new ArgumentNullException("type");
            }

			object defaultValue = GetDefaultValue(type);

			

        	return value == null ||  type.IsValueType && value.Equals(defaultValue);
        }

        /// <summary>
        /// Returns a type name that consists of the assembly name and the full name of the specified type (type name with namespace)
        /// </summary>
        /// <param name="type">The type from which the simple type name should be retrieved</param>
        /// <returns>Returns a simple type name</returns>
        public static string GetSimpleTypeName(Type type) {
            if (type == null) {
                return "Type object is null";
            }
            return string.Format("{0}, {1}", type.FullName, type.Assembly.GetName().Name);
        }

        /// <summary>
        /// Returns a type from the generic type definitions of a given type.
        /// It also searches all implemented interfaces for generic type definitions.
        /// The type must be assignable from the type given with the parameter mustAssignableType
        /// </summary>
        /// <param name="genericType">The type that have to be scanned for generic type arguments</param>
        /// <param name="mustAssignableType">The type the generic type argument must match with</param>
        /// <returns>Returns the searched generic type argument</returns>
        public static Type GetGenericTypeArgument(Type genericType, Type mustAssignableType) {
            #region Input Validation
            if (genericType == null) {
                return null;
            }
            if (mustAssignableType == null) {
                return null;
            }
            #endregion

            if (genericType.IsGenericType) {
                foreach (Type foundGenericType in genericType.GetGenericArguments()) {
                    if (mustAssignableType.IsAssignableFrom(foundGenericType)) {
                        return foundGenericType;
                    }
                }
            }

            foreach (Type interfaceType in genericType.GetInterfaces()) {
                Type foundType = GetGenericTypeArgument(interfaceType, mustAssignableType);
                if (foundType != null) {
                    return foundType;
                }
            }
            return null;
        }


        
        public static string[] GetTypeComponents(string fullTypeName) {
            string[] typeComponents = fullTypeName.Split(new char[] { ',' });
            if (typeComponents.Length < 2) {
                throw new InvalidOperationException("Invalid parameter <typeName> - " +
                                                    " The Parameter must have the default .NET type format " +
                                                    "(\"TypeName, AssemblyName, Version, Culture, PublicKeyToken\")");
            }

            return typeComponents;
        }

        

        /// <summary>
        /// Creates an instance using the given constructor.
        /// If the constructor has some parameter defined - the parameter values will be initialize with default values
        /// </summary>
        /// <param name="constructor">The constructor to use for creating new instances</param>
        /// <returns>Returns a new instance using the given constructor</returns>
        public static object CreateDefaultInstance(Type type) {
			if(type.IsAbstract) {
				return null;
			}

            if (HasDefaultConstructor(type)) {
                return Activator.CreateInstance(type);
            }

            ConstructorInfo ParametrizedConstructor = GetFirstParametrizedConstructor(type);
            object[] ConstructorValues = new object[ParametrizedConstructor.GetParameters().Length];


            return ParametrizedConstructor.Invoke(ConstructorValues);

        }

        /// <summary>
        /// Creates an instance using the given constructor.
        /// If the constructor has some parameter defined - the parameter values will be initialize with default values
        /// </summary>
        /// <typeparam name="T">The type to create</typeparam>
        /// <returns>
        /// Returns a new instance using the given constructor
        /// </returns>
        public static T CreateDefaultInstance<T>() {
            return (T)CreateDefaultInstance(typeof(T));
        }


        /// <summary>
        /// Creates an instance using the given constructor.
        /// If the constructor has some parameter defined - the parameter values will be initialize with default values
        /// </summary>
        /// <param name="type">The type to create.</param>
        /// <param name="createDefaultInstanceForFirstLevelProperties">Indicates whether a default instance is to be created for each property on the first level</param>
        /// <returns>Returns a new instance using the given constructor</returns>
        public static object CreateDefaultInstance(Type type, bool createDefaultInstanceForFirstLevelProperties) {

            object RetVal = CreateDefaultInstance(type);

            if (createDefaultInstanceForFirstLevelProperties)
                foreach (PropertyInfo Property in RetVal.GetType().GetProperties()) {
                    if (!Property.PropertyType.IsValueType && Property.CanWrite && !Property.PropertyType.IsAbstract && !Property.PropertyType.IsInterface)
                        Property.SetValue(RetVal, CreateDefaultInstance(Property.PropertyType), null);
                }

            return RetVal;
        }





        public static Dictionary<Type, TPropertyValue> GetStaticPropertyValueTypeDict<TPropertyValue>(Type[] typesToSearch) {

            Dictionary<Type, TPropertyValue> PropertyDict =
                new Dictionary<Type, TPropertyValue>();

            foreach (Type instanceBuilderType in typesToSearch) {
                foreach (PropertyInfo prop in instanceBuilderType.
                    GetProperties(BindingFlags.Public | BindingFlags.Static)) {
                    if (typeof(TPropertyValue).IsAssignableFrom(prop.PropertyType)) {

                        PropertyDict.Add(prop.PropertyType, (TPropertyValue)prop.GetValue(null, null));
                    }
                }

            }

            return PropertyDict;
        }



        public static List<TPropertyValue> GetStaticPropertyValueTypeList<TPropertyValue>(Type[] typesToSearch) {

            List<TPropertyValue> PropertyList =
                new List<TPropertyValue>();

            foreach (Type instanceBuilderType in typesToSearch) {
                foreach (PropertyInfo prop in instanceBuilderType.
                    GetProperties(BindingFlags.Public | BindingFlags.Static)) {
                    if (typeof(TPropertyValue).IsAssignableFrom(prop.PropertyType)) {

                        PropertyList.Add((TPropertyValue)prop.GetValue(null, null));
                    }
                }

            }

            return PropertyList;
        }


        /// <summary>
        /// Determines whether the given object is a list or an array.
        /// </summary>
        /// <param name="objectToCheck">The object to check.</param>
        /// <param name="isPrimitive">if set to <c>true</c> it will also check if the element type is a primitve type.</param>
        /// <returns>
        /// 	<c>true</c> if [the specified objetct to check] [is list or array] ; otherwise, <c>false</c>.
        /// </returns>
        public static bool IsListOrArray(object objectToCheck, bool isPrimitive) {

            if (objectToCheck == null)
                return false;

            // Getting the type of the object
            Type objectType = objectToCheck.GetType();

            if (objectType.IsArray) {
                Type ElementType = objectType.GetElementType();

                if (ElementType == null)
                    return false;

                if (isPrimitive)
                    return ElementType.IsPrimitive;
                else
                    return true;
            }
            else {
                // Getting the generic types of the list
                Type[] Arguments = objectType.GetGenericArguments();

                if (Arguments == null || Arguments.Length < 1)
                    return false;

                // getting the first one, since IList<T> uses only one.
                Type GenericArg = Arguments[0];

                return (typeof(System.Collections.IList).IsAssignableFrom(objectType) && isPrimitive ? GenericArg.IsPrimitive : true);
            }
        }

        public static Type[] GetPropertyTypes(PropertyInfo[] properties) {
            Type[] PropertyTypes = new Type[properties.Length];
            for (int i = 0; i < properties.Length; i++) {
                PropertyTypes[i] = properties[i].PropertyType;
            }
            return PropertyTypes;
        }

        public static object ConvertType(object source, Type targetType) {
            if (targetType == typeof(bool)) {
                return Convert.ToBoolean(source);
            }
            else if (targetType == typeof(int)) {
                return Convert.ToInt32(source);
            }
            else if (targetType == typeof(long)) {
                return Convert.ToInt64(source);
            }
            else if (targetType == typeof(short)) {
                return Convert.ToInt16(source);
            }
            else if (targetType == typeof(ulong)) {
                return Convert.ToUInt64(source);
            }
            else if (targetType == typeof(decimal)) {
                return Convert.ToDecimal(source);
            }
            else if (targetType == typeof(double)) {
                return Convert.ToDouble(source);
            }
            else if (targetType == typeof(float)) {
                return Convert.ToSingle(source);
            }
            else if (targetType == typeof(string)) {
                return Convert.ToString(source);
            }
            else if (targetType == typeof(char)) {
                return Convert.ToChar(source);
            }
            else if (targetType == typeof(byte)) {
                return Convert.ToByte(source);
            }
            return source;
        }


        #region Attribute Helper


        /// <summary>
        /// Lists the attributes of the given property for the type <typeparamref name="TAttr"/>.
        /// </summary>
        /// <typeparam name="TAttr">The type of the attribute</typeparam>
        /// <param name="property">The property to scan for attributes of type <typeparamref name="TAttr"/>.</param>
        /// <returns>Returns null if no attribute was found</returns>
        public static IList<TAttr> ListAttributes<TAttr>(PropertyInfo property) where TAttr : Attribute {

            object[] attributes = property.GetCustomAttributes(typeof(TAttr), false);
            if (ValidationHelper.IsNullOrEmpty(attributes)) {
                return null;
            }

            return attributes.Cast<TAttr>().ToList<TAttr>();
        }

        /// <summary>
        /// Gets the first occurance of the attribute of type <typeparamref name="TAttr"/> for the given property.
        /// </summary>
        /// <typeparam name="TAttr">The type of the attribute</typeparam>
        /// <param name="property">The property to scan for attributes of type <typeparamref name="TAttr"/>.</param>
        /// <returns>Returns null if no attribute was found</returns>
        public static TAttr GetFirstAttribute<TAttr>(PropertyInfo property) where TAttr : Attribute {

            object[] attributes = property.GetCustomAttributes(typeof(TAttr), false);
            if (ValidationHelper.IsNullOrEmpty(attributes)) {
                return null;
            }

            return (TAttr)attributes[0];
        }

        public static TReturnType GetValueOfProperty<TAttributeType, TReturnType>(object instancedObject) where TAttributeType : Attribute {

            PropertyInfo[] Properties = instancedObject.GetType().GetProperties();

            foreach (PropertyInfo Property in Properties) {
                TAttributeType[] Attributes = (TAttributeType[])Property.GetCustomAttributes(typeof(TAttributeType), true);

                if (Attributes.Length > 0) {
                    try {
                        return (TReturnType)Property.GetValue(instancedObject, null);
                    }
                    catch (InvalidCastException exc) {
                        throw new Exception(string.Format("Wrong type as TReturnType declared. The value of the property has not the type <{0}>", typeof(TReturnType).ToString()), exc);
                    }
                }

            }

            return default(TReturnType);
        }


        #endregion


        #region Constructor Helper
        /// <summary>
        /// Determines whether the specified type has a default constructor
        /// </summary>
        /// <param name="type">The type to check</param>
        /// <returns>
        /// 	<c>true</c> if the specified type has a default constructor otherwise, <c>false</c>.
        /// </returns>
        public static bool HasDefaultConstructor(Type type) {
            ConstructorInfo[] Constructors = type.GetConstructors();
            //if there is no constructor defined explicitly there must be at least a default constructor
            if (ValidationHelper.IsNullOrEmpty(Constructors)) {
                return true;
            }
            //if there are any constructors available check if there is one with no parameters
            foreach (ConstructorInfo Constructor in Constructors) {
                if (ValidationHelper.IsNullOrEmpty(Constructor.GetParameters())) {
                    return true;
                }
            }
            return false;
        }

        /// <summary>
        /// Gets the first parametrized constructor of the given type.
        /// </summary>
        /// <param name="type">The type to check</param>
        /// <returns></returns>
        public static ConstructorInfo GetFirstParametrizedConstructor(Type type) {
            ConstructorInfo[] Constructors = type.GetConstructors();
            foreach (ConstructorInfo Constructor in Constructors) {
                if (!ValidationHelper.IsNullOrEmpty(Constructor.GetParameters())) {
                    return Constructor;
                }
            }
            return null;
        }

        

        #endregion
    }
}


